package nus.iss.ga.ui;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Font;
import java.awt.GridLayout;
import java.awt.Window;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
import java.awt.event.WindowListener;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
import java.io.PrintStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTabbedPane;
import javax.swing.JTable;
import javax.swing.JTextArea;
import javax.swing.JTextField;
import javax.swing.SwingUtilities;

import nus.iss.ga.GAController;
import nus.iss.ga.controller.common.IGAConsole;
import nus.iss.ga.controller.common.IGAController;
import nus.iss.ga.controller.model.Container;

public class GAMain extends JPanel implements WindowListener, IGAConsole, Runnable {
	private static final long serialVersionUID = 1046566583716935759L;

	private ExecutorService executor = Executors.newFixedThreadPool(5);

	private String datapath;
	static private JTable table;
	IGAController gaController = new GAController(this);
	Properties prop;
	private JTextArea textArea;
	private Thread reader;
	private Thread reader2;
	private boolean quit;

	private final PipedInputStream pin = new PipedInputStream();
	private final PipedInputStream pin2 = new PipedInputStream();

	private JTextField populationValue;

	private JTextField crossOverValue;

	private JTextField mutationValue;

	private JTextField EvolutionValue;

	private JTextField weight_penalty_value;

	private JTextField dest_penalty_value;

	private JTextField type_penalty_value;

	private JTextField state_penalty_value;

	private JTextField resultValue;

	public GAMain(String dataFilePath) {
		// JTable table;
		DataFileTableModel model;
		datapath = dataFilePath;
		Font f;
		f = new Font("SanSerif", Font.PLAIN, 24);
		setFont(f);
		setLayout(new BorderLayout());

		model = new DataFileTableModel(dataFilePath);

		prop = new Properties();

		try {
			// load a properties file
			prop.load(new FileInputStream("package.properties"));

			// get the property value and print it out
			System.out.println(prop.getProperty("Fragile"));
			System.out.println(prop.getProperty("Non-Fragile"));
			System.out.println("Solid =" + prop.getProperty("Solid"));

		} catch (IOException ex) {
			ex.printStackTrace();
		}

		table = new JTable();
		table.setModel(model);

		table.setDefaultEditor(Value.class, new ValueEditor());

		table.createDefaultColumnsFromModel();

		JScrollPane scrollpane = new JScrollPane(table);
		scrollpane.setPreferredSize(new Dimension(200, 650));
		add(constructTopPanel(), BorderLayout.NORTH);
		add(scrollpane);
		add(constructButtonPanel(), BorderLayout.PAGE_END);

		try {
			PipedOutputStream pout2 = new PipedOutputStream(this.pin2);
			System.setErr(new PrintStream(pout2, true));
		} catch (java.io.IOException io) {
			textArea.append("Couldn't redirect STDERR to this console\n" + io.getMessage());
		} catch (SecurityException se) {
			textArea.append("Couldn't redirect STDERR to this console\n" + se.getMessage());
		}
		// Thread Implementation
		quit = false; // signals the Threads that they should exit

		// Starting two separate threads to read from the PipedInputStreams
		//
		reader = new Thread(this);
		reader.setDaemon(true);
		reader.start();
		//
		reader2 = new Thread(this);
		reader2.setDaemon(true);
		reader2.start();
		System.out.println("Starting GA Run operation....");

	}

	private JPanel constructTitlePanel() {
		JPanel titlePanel = new JPanel();
		Font titleFont = new Font("Arial", Font.BOLD, 14);
		JLabel title = new JLabel("Load Distribution Optimization (Using concept of Genetic Algorithms)");
		title.setFont(titleFont);
		titlePanel.setLayout(new FlowLayout(FlowLayout.CENTER));
		titlePanel.add(title);

		return titlePanel;

	}

	private JPanel constructTopPanel() {

		Font font = new Font("Arial", Font.PLAIN, 12);
		Font boldFont = new Font("Arial", Font.BOLD, 12);

		JPanel titleContainer = new JPanel();
		JPanel topPanel = new JPanel();
		JPanel firstColumn = new JPanel();
		GridLayout topcontainer = new GridLayout(0, 1); // First column for
														// Configuration
														// properties, second
														// column for other
														// parameters
		GridLayout topContainerFirstColumn = new GridLayout(0, 6);

		topPanel.setLayout(topcontainer);
		firstColumn.setLayout(topContainerFirstColumn);
		titleContainer.setLayout(new BorderLayout());

		// Constructing first column fields and text boxes (i.e. CrossOver,
		// Mutation and Initial Population)
		JLabel configurationTitle = new JLabel(" Run Configuration");
		configurationTitle.setFont(boldFont);
		JLabel empty1 = new JLabel("");
		JLabel empty2 = new JLabel("");
		JLabel empty3 = new JLabel("");
		JLabel empty4 = new JLabel("");
		JLabel empty5 = new JLabel("");
		JLabel empty6 = new JLabel("");
		JLabel empty7 = new JLabel("");
		JLabel empty8 = new JLabel("");
		JLabel empty9 = new JLabel("");
		JLabel empty10 = new JLabel("");
		JLabel empty11 = new JLabel("");
		JLabel empty12 = new JLabel("");
		JLabel empty13 = new JLabel("");
		JLabel empty14 = new JLabel("");
		JLabel empty15 = new JLabel("");
		JLabel empty16 = new JLabel("");
		JLabel empty17 = new JLabel("");
		JLabel empty18 = new JLabel("");
		JLabel empty19 = new JLabel("");
		JLabel empty20 = new JLabel("");
		JLabel empty21 = new JLabel("");
		JLabel empty22 = new JLabel("");
		JLabel empty23 = new JLabel("");
		JLabel empty24 = new JLabel("");

		JLabel population = new JLabel(" Population");
		population.setFont(font);
		populationValue = new JTextField(String.valueOf(gaController.getPopulationSize()));
		// JTextField populationValue = new JTextField(52);
		JLabel crossOver = new JLabel(" ");
		crossOver.setFont(font);
		crossOverValue = new JTextField("");
		crossOverValue.setColumns(5);
		crossOverValue.setEnabled(false);
		JLabel Mutation = new JLabel(" Mutation(1/X)");
		Mutation.setFont(font);
		mutationValue = new JTextField(String.valueOf(gaController.getMutationRate()));
		mutationValue.setColumns(5);
		JLabel containerSpecification = new JLabel("Container Specification");
		containerSpecification.setFont(boldFont);

		JLabel evolution = new JLabel("Evolution(k)");
		evolution.setFont(font);
		EvolutionValue = new JTextField(String.valueOf(gaController.getNumOfEvolutionInThousand()));

		JLabel weight_penalty = new JLabel("Penalty(Weight)");
		weight_penalty.setFont(font);
		weight_penalty_value = new JTextField(String.valueOf(gaController.getPenaltyForHeavierAtTop()));

		JLabel dest_penalty = new JLabel("Penalty(Destination)");
		dest_penalty.setFont(font);
		dest_penalty_value = new JTextField(String.valueOf(gaController.getPenaltyForDestination()));

		JLabel type_penalty = new JLabel("Penalty(Type)");
		type_penalty.setFont(font);
		type_penalty_value = new JTextField(String.valueOf(gaController.getPenaltyForItemType()));

		JLabel state_penalty = new JLabel("Penalty(State)");
		state_penalty.setFont(font);
		state_penalty_value = new JTextField(String.valueOf(gaController.getPenaltyForItemState()));

		// Row1
		firstColumn.add(configurationTitle);
		firstColumn.add(empty1);
		firstColumn.add(empty2);
		firstColumn.add(empty3);
		firstColumn.add(empty4);
		firstColumn.add(empty5);

		// Row2
		firstColumn.add(population);
		firstColumn.add(populationValue);
		firstColumn.add(empty6);
		firstColumn.add(evolution);
		firstColumn.add(EvolutionValue);
		firstColumn.add(empty7);

		// Row3
		firstColumn.add(crossOver);
		firstColumn.add(crossOverValue);
		firstColumn.add(empty8);
		firstColumn.add(weight_penalty);
		firstColumn.add(weight_penalty_value);
		firstColumn.add(empty9);

		// Row4
		firstColumn.add(Mutation);
		firstColumn.add(mutationValue);
		firstColumn.add(empty10);
		firstColumn.add(dest_penalty);
		firstColumn.add(dest_penalty_value);
		firstColumn.add(empty11);

		// Row5
		firstColumn.add(empty12);
		firstColumn.add(empty13);
		firstColumn.add(empty14);
		firstColumn.add(type_penalty);
		firstColumn.add(type_penalty_value);
		firstColumn.add(empty15);

		// Row5
		firstColumn.add(containerSpecification);
		firstColumn.add(empty16);
		firstColumn.add(empty17);
		firstColumn.add(state_penalty);
		firstColumn.add(state_penalty_value);
		firstColumn.add(empty18);

		firstColumn.add(empty19);
		firstColumn.add(empty20);
		firstColumn.add(empty21);
		firstColumn.add(empty22);
		firstColumn.add(empty23);
		firstColumn.add(empty24);

		topPanel.add(firstColumn);
		// topPanel.add(secondColumn);

		titleContainer.add(constructTitlePanel(), BorderLayout.NORTH);
		titleContainer.add(topPanel, BorderLayout.CENTER);
		// topPanel.add
		return titleContainer;

	}

	private JPanel constructButtonPanel() {
		JPanel consolePanel = new JPanel();
		JPanel consoleScrollPanel = new JPanel();
		JPanel buttonPanel = new JPanel();
		JPanel resultPanel = new JPanel();
		GridLayout buttoncontainer = new GridLayout(0, 1);
		GridLayout resultPanelcontainer = new GridLayout(0, 2);
		consoleScrollPanel.setLayout(new GridLayout(0, 3));
		buttonPanel.setLayout(buttoncontainer);
		resultPanel.setLayout(resultPanelcontainer);
		// consolePanel.setLayout(new GridLayout(0,2));
		consolePanel.setLayout(new BorderLayout());

		JLabel blank1 = new JLabel("");
		JLabel blank2 = new JLabel("");
		JLabel blank3 = new JLabel("");
		JLabel blank4 = new JLabel("");
		JLabel blank5 = new JLabel("");
		JLabel blank6 = new JLabel("");
		JLabel blank7 = new JLabel("");
		JLabel blank8 = new JLabel("");
		JLabel blank9 = new JLabel("");

//		JButton reset = new JButton("Reset");
		final JButton run = new JButton("Run");
		JButton console = new JButton("Console");
		JButton stop = new JButton("Stop");

		JLabel result = new JLabel("\u2211(xi-AV)2 ");
		resultValue = new JTextField("50");

		resultPanel.add(result);
		resultPanel.add(resultValue);

		buttonPanel.add(blank1);
		buttonPanel.add(blank2);
		buttonPanel.add(blank3);
		buttonPanel.add(blank4);
		buttonPanel.add(blank5);
		buttonPanel.add(blank6);
		buttonPanel.add(blank7);
		buttonPanel.add(blank8);
		buttonPanel.add(blank9);

		buttonPanel.add(resultPanel);
//		buttonPanel.add(reset);
		buttonPanel.add(run);
		buttonPanel.add(stop);

		// Buildinng Console here
		textArea = new JTextArea();
		textArea.setEditable(false);
		textArea.append("Console..");

		JTabbedPane jtbConsole = new JTabbedPane();
		JScrollPane consoleScrollPane = new JScrollPane(textArea);
		consoleScrollPane.setPreferredSize(new Dimension(750, 150));
		jtbConsole.addTab("Console", consoleScrollPane);

		consolePanel.add(jtbConsole, BorderLayout.WEST);
		consolePanel.add(buttonPanel, BorderLayout.EAST);

		stop.setBounds(40, 30, 80, 25);
		stop.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				// Execute when button is pressed
				System.out.println("Stopping GA Run operation");
				gaController.stop();

			}
		});

		run.setBounds(40, 30, 80, 25);
		run.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				System.out.println("Uptating Table");
				writeConsole("Started Uptating Table");
				System.out.println("Preparaing containerList");
				run.setEnabled(false);
				executor.execute(new Runnable() {

					@Override
					public void run() {
						// Execute when button is pressed
						System.out.println("You clicked the button");
						final List<Container> resultList = getResultList();

						SwingUtilities.invokeLater(new Runnable() {

							@Override
							public void run() {
								populateResults(resultList);
								System.out.println("Displaying Results");
								System.out.println("Result size: " + resultList);
								/**
								 * disable, allowed only run once.
								 */
//								run.setEnabled(true);
							}

						});
					}

				});

			}
		});

//		reset.setBounds(40, 30, 80, 25);
//		reset.addActionListener(new ActionListener() {
//
//			public void actionPerformed(ActionEvent e) {
//				// Execute when button is pressed
//				System.out.println("Resetting table to original value");
//				DataFileTableModel newmodel = new DataFileTableModel(datapath);
//				table.setModel(newmodel);
//				textArea.setText("");
//			}
//		});

		console.setBounds(40, 30, 80, 25);
		console.addActionListener(new ActionListener() {

			public void actionPerformed(ActionEvent e) {
				// Execute when button is pressed
				System.out.println("Displaying console");
				GAConsole gaConsole = new GAConsole();
				gaConsole.writeConsole("Calling GAController...");
			}
		});

		return consolePanel;
	}

	private List<Container> getResultList() {

		List<Container> containerList = new ArrayList<Container>();
		System.out.println("Preparaing result list");
		List<Container> resultList = new ArrayList<Container>();

		System.out.println("table row count " + table.getRowCount());

		System.out.println("Row 1 values " + (String) table.getModel().getValueAt(0, 0) + " | " + (String) table.getModel().getValueAt(0, 1) + " | "
				+ (String) table.getModel().getValueAt(0, 2) + " | " + (String) table.getModel().getValueAt(0, 3) + " | " + (String) table.getModel().getValueAt(0, 4)

		);
		System.out.println("Row 1/2 values " + prop.getProperty((String) table.getModel().getValueAt(0, 2)));
		System.out.println("Row 1/3 values " + prop.getProperty((String) table.getModel().getValueAt(0, 3)));
		System.out.println("Row 1/4 values " + prop.getProperty((String) table.getModel().getValueAt(0, 4)));
		for (int i = 0; i < table.getRowCount(); i++) {
			containerList.add(getContainer(getIntSelectedValue(i, 0), getIntSelectedValue(i, 1), prop.getProperty(getStringSelectedValue(i, 2)),
					prop.getProperty(getStringSelectedValue(i, 3)), prop.getProperty(getStringSelectedValue(i, 4)))); // "A"
																														// means
																														// destination,
																														// 1
																														// means
																														// fragile
		}

		for (int j = 0; j < containerList.size(); j++) {
			System.out.println("Displaying Results" + containerList.get(j).getNumber() + "|" + containerList.get(j).getDestination() + "|" + containerList.get(j).getDestination());

		}
		writeConsole("Calling GAController...");
		gaController.setMutationRate(Integer.valueOf(mutationValue.getText()));
		gaController.setPopulationSize(Integer.valueOf(populationValue.getText()));
		gaController.setNumOfEvolutionInThousand(Integer.valueOf(EvolutionValue.getText()));
		gaController.setPenaltyForDestination(Double.valueOf(dest_penalty_value.getText()));
		gaController.setPenaltyForHeavierAtTop(Double.valueOf(weight_penalty_value.getText()));
		gaController.setPenaltyForItemState(Double.valueOf(state_penalty_value.getText()));
		gaController.setPenaltyForItemType(Double.valueOf(type_penalty_value.getText()));
		gaController.setContainerInList(containerList);
		gaController.runGA();
		resultList = gaController.getResults();
		return resultList;

	}

	public void populateResults(List<Container> results) {
		Vector<String> columnNames = new Vector<String>();
		Vector<String> resultdata = new Vector<String>();

		try {
			FileInputStream fin = new FileInputStream(datapath);
			BufferedReader br = new BufferedReader(new InputStreamReader(fin));
			// extract column names
			StringTokenizer st1 = new StringTokenizer(br.readLine(), "|");
			br.close();
			while (st1.hasMoreTokens())
				columnNames.addElement(st1.nextToken());

			for (int i = 0; i < results.size(); i++) {
				resultdata.addElement(String.valueOf(results.get(i).getNumber()));
				resultdata.addElement(String.valueOf(results.get(i).getWeight()));
				resultdata.addElement(prop.getProperty("Dest_" + String.valueOf(results.get(i).getDestination())));
				resultdata.addElement(prop.getProperty("Type_" + String.valueOf(results.get(i).getTypeOfPackage())));
				resultdata.addElement(prop.getProperty("State_" + String.valueOf(results.get(i).getStateOfPackage())));

				resultdata.addElement((String) table.getModel().getValueAt(i, 5));
				resultdata.addElement((String) table.getModel().getValueAt(i, 6));
				resultdata.addElement((String) table.getModel().getValueAt(i, 7));

			}

			DataFileTableModel resultsModel = new DataFileTableModel(columnNames, resultdata);
			table.setModel(resultsModel);

		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public int getIntSelectedValue(int row, int col) {
		return Integer.parseInt((String) table.getModel().getValueAt(row, col));
	}

	public String getStringSelectedValue(int row, int col) {
		return (String) table.getModel().getValueAt(row, col);
	}

	private nus.iss.ga.controller.model.Container getContainer(int number, int weight, String dest, String type, String state) {
		nus.iss.ga.controller.model.Container cont = new Container();
		cont.setNumber(number);
		cont.setWeight(weight);
		cont.setDestination(dest);
		cont.setTypeOfPackage(type);
		cont.setStateOfPackage(state);
		return cont;

	}

	public Dimension getPreferredSize() {
		return new Dimension(900, 700);
	}

	public static void main(String s[]) {
		JFrame frame = new JFrame("Data File Table");
		GAMain panel;

		panel = new GAMain("package.dat");

		frame.setDefaultCloseOperation(JFrame.DO_NOTHING_ON_CLOSE);
		frame.setForeground(Color.black);
		frame.setBackground(Color.lightGray);
		frame.getContentPane().add(panel, "Center");

		frame.setSize(panel.getPreferredSize());
		frame.setVisible(true);
		frame.addWindowListener(new WindowCloser());
	}

	// Thread Methods
	public synchronized void windowClosed(WindowEvent evt) {
		quit = true;
		this.notifyAll(); // stop all threads
		try {
			reader.join(1000);
			pin.close();
		} catch (Exception e) {
		}
		try {
			reader2.join(1000);
			pin2.close();
		} catch (Exception e) {
		}
		System.exit(0);
	}

	public synchronized void windowClosing(WindowEvent evt) {
		// frame.setVisible(false); // default behaviour of JFrame
		// frame.dispose();
	}

	public synchronized void actionPerformed(ActionEvent evt) {
		textArea.setText("");
	}

	public synchronized void run() {
		try {
			while (Thread.currentThread() == reader) {
				try {
					this.wait(100);
				} catch (InterruptedException ie) {
				}
				if (pin.available() != 0) {
					String input = this.readLine(pin);
					textArea.append(input);
				}
				if (quit)
					return;
			}

			while (Thread.currentThread() == reader2) {
				try {
					this.wait(100);
				} catch (InterruptedException ie) {
				}
				if (pin2.available() != 0) {
					String input = this.readLine(pin2);
					textArea.append(input);
				}
				if (quit)
					return;
			}
		} catch (Exception e) {
			textArea.append("\nConsole reports an Internal error.");
			textArea.append("The error is: " + e);
		}

	}

	public synchronized String readLine(PipedInputStream in) throws IOException {
		String input = "";
		do {
			int available = in.available();
			if (available == 0)
				break;
			byte b[] = new byte[available];
			in.read(b);
			input = input + new String(b, 0, b.length);
		} while (!input.endsWith("\n") && !input.endsWith("\r\n") && !quit);
		return input;
	}

	public void writeConsole(String text) {
		System.out.println("\n" + text);
		textArea.append("\n" + text);

	}

	@Override
	public void windowActivated(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowDeactivated(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowDeiconified(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowIconified(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void windowOpened(WindowEvent e) {
		// TODO Auto-generated method stub

	}

	@Override
	public void outputToConsole(final String text) {
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				textArea.append("\n" + text);
				textArea.setCaretPosition(textArea.getText().length());
			}
		});
	}

	@Override
	public void outputToFitnessValue(final Double text) {
		SwingUtilities.invokeLater(new Runnable() {

			@Override
			public void run() {
				resultValue.setText(String.format("%.2f", text));
				
			}
			
		});
	}
}

class WindowCloser extends WindowAdapter {
	public void windowClosing(WindowEvent e) {
		Window win = e.getWindow();
		win.setVisible(false);
		System.exit(0);
	}
}
